package com.fary.security.config.annotation.authentication.configuration;

import com.fary.beans.factory.BeanFactoryUtils;
import com.fary.beans.factory.annotation.Autowired;
import com.fary.context.ApplicationContext;
import com.fary.context.ConfigurableApplicationContext;
import com.fary.context.annotation.Bean;
import com.fary.context.annotation.Configuration;
import com.fary.context.annotation.Import;
import com.fary.core.SpringException;
import com.fary.core.annotation.AnnotationAwareOrderComparator;
import com.fary.security.authentication.AuthenticationEventPublisher;
import com.fary.security.authentication.AuthenticationManager;
import com.fary.security.config.annotation.ObjectPostProcessor;
import com.fary.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import com.fary.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import com.fary.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
import com.fary.security.core.Authentication;
import com.fary.security.core.AuthenticationException;
import com.fary.security.crypto.PasswordEncoder;
import com.fary.security.core.userdetails.UserDetailsService;
import com.fary.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 这个类的作用就是用来创建ProviderManager，ProviderManager是一个AuthenticationManager实现，用于管理所有AuthenticationProvider实现的一个管理器
 */
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

    // 标志位，AuthenticationManager是否正处于构建过程中
    private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();

    // Application容器
    private ApplicationContext applicationContext;

    // 用于记录所要构建的AuthenticationManager
    private AuthenticationManager authenticationManager;

    // AuthenticationManager是否已经被构建的标志
    private boolean authenticationManagerInitialized;

    // 全局认证配置适配器列表，三个默认的GlobalAuthenticationConfigurerAdapter实现会被注入到这个属性列表中
    private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();

    // 对象后处理器
    private ObjectPostProcessor<Object> objectPostProcessor;

    @Bean
    public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
        /**
         * Lazy密码加密器：该对象创建时容器中可能还不存在真正的密码加密器
         * 但是用该lazy密码加密器进行加密或者密码匹配时，会从容器中获取类型为PasswordEncoder的密码加密器,
         * 如果容器中不存在类型为PasswordEncoder的密码加密器,则使用
         * PasswordEncoderFactories.createDelegatingPasswordEncoder()创建一个PasswordEncoder供随后加密或者密码匹配使用
         * LazyPasswordEncoder是定义在当前配置类中的一个内部类
         */
        LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
        /**
         *获取鉴权事件的发布器
         */
        AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

        /**
         * 生成AuthenticationManagerBuilder实例，使用实现类为DefaultPasswordEncoderAuthenticationManagerBuilder
         * DefaultPasswordEncoderAuthenticationManagerBuilder是定义在该配置类中的一个内部类,它继承自AuthenticationManagerBuilder
         * 是SpringSecurity缺省使用的 AuthenticationManagerBuilder实现类
         */
        DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
        /**
         *如果有事件发布器，则设置
         */
        if (authenticationEventPublisher != null) {
            result.authenticationEventPublisher(authenticationEventPublisher);
        }
        return result;
    }

    // EnableGlobalAuthenticationAutowiredConfigurer是GlobalAuthenticationConfigurerAdapter的一个实现，当前配置类的内部类。

    /**
     * 会加载使用了注解@EnableGlobalAuthentication的Bean，用于配置全局AuthenticationManagerBuilder
     */
    @Bean
    public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
        return new EnableGlobalAuthenticationAutowiredConfigurer(context);
    }

    /**
     * 用于在没有配置单例的UserDetailsService时延时配置全局AuthenticationManagerBuilder
     */
    @Bean
    public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        return new InitializeUserDetailsBeanManagerConfigurer(context);
    }

    /**
     * 用于在没有配置单例的UserDetailsService时延时加载全局AuthenticationProvider
     */
    @Bean
    public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
        return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    }

    public AuthenticationManager getAuthenticationManager() throws Exception {
        // authenticationManager如果已经被构建则直接返回authenticationManager
        if (this.authenticationManagerInitialized) {
            return this.authenticationManager;
        }
        // 获取容器中的AuthenticationManagerBuilder实例用于创建AuthenticationManager
        AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
        // 如果已经正在使用authBuilder进行构建, 则这里直接返回一个包装了构建器authBuilder的AuthenticationManagerDelegator对象
        // true表示现在正在构建过程中，false表示现在不在构建过程中
        if (this.buildingAuthenticationManager.getAndSet(true)) {
            return new AuthenticationManagerDelegator(authBuilder);
        }

        // 将全局配置设置到AuthenticationManagerBuilder中
        for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
            authBuilder.apply(config);
        }

        // 构建AuthenticationManager
        authenticationManager = authBuilder.build();

        //  如果容器中没有用于构建 AuthenticationManager 的 AuthenticationProvider bean
        //  供 authBuilder 使用,也没有为 authBuilder 设置 parent AuthenticationManager 时,
        //  则上面产生的 authenticationManager 为 null 。 不过这种情况缺省情况下并不会发生,
        //  因为该配置类中 bean InitializeUserDetailsBeanManagerConfigurer 为 authBuilder
        //  添加的 InitializeUserDetailsBeanManagerConfigurer 会在这种情况下构造一个
        //  DaoAuthenticationProvider 对象给 authBuilder 使用。另外，一般情况下，开发人员也会
        // 提供自己的 AuthenticationProvider 实现类。
        // 通常经过上面的 authBuilder.build()，authenticationManager 对象都会被创建,
        // 但是如果 authenticationManager 未被创建，这里尝试使用 getAuthenticationManagerBean()
        // 再次设置 authenticationManager
        if (authenticationManager == null) {
            authenticationManager = getAuthenticationManagerBean();
        }

        // 将authenticationManagerInitialized 设置为true，说明authenticationManager已经初始化完成
        this.authenticationManagerInitialized = true;
        // 返回构建好的AuthenticationManager
        return authenticationManager;
    }

    @Autowired(required = false)
    public void setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter> configurers) {
        configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.globalAuthConfigurers = configurers;
    }

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Autowired
    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
        this.objectPostProcessor = objectPostProcessor;
    }

    @SuppressWarnings("unchecked")
    private <T> T lazyBean(Class<T> interfaceName) {
        LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
        String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, interfaceName);
        if (beanNamesForType.length == 0) {
            return null;
        }
        String beanName;
        if (beanNamesForType.length > 1) {
            List<String> primaryBeanNames = getPrimaryBeanNames(beanNamesForType);

            Assert.isTrue(primaryBeanNames.size() != 0, () -> "Found " + beanNamesForType.length + " beans for type " + interfaceName + ", but none marked as primary");
            Assert.isTrue(primaryBeanNames.size() == 1, () -> "Found " + primaryBeanNames.size() + " beans for type " + interfaceName + " marked as primary");
            beanName = primaryBeanNames.get(0);
        } else {
            beanName = beanNamesForType[0];
        }

        lazyTargetSource.setTargetBeanName(beanName);
        lazyTargetSource.setBeanFactory(applicationContext);
        ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
        proxyFactory = objectPostProcessor.postProcess(proxyFactory);
        proxyFactory.setTargetSource(lazyTargetSource);
        return (T) proxyFactory.getObject();
    }

    private List<String> getPrimaryBeanNames(String[] beanNamesForType) {
        List<String> list = new ArrayList<>();
        if (!(applicationContext instanceof ConfigurableApplicationContext)) {
            return Collections.emptyList();
        }
        for (String beanName : beanNamesForType) {
            if (((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName).isPrimary()) {
                list.add(beanName);
            }
        }
        return list;
    }

    private AuthenticationManager getAuthenticationManagerBean() {
        return lazyBean(AuthenticationManager.class);
    }

    private static <T> T getBeanOrNull(ApplicationContext applicationContext, Class<T> type) {
        try {
            return applicationContext.getBean(type);
        } catch (SpringException notFound) {
            return null;
        }
    }

    private static class EnableGlobalAuthenticationAutowiredConfigurer extends GlobalAuthenticationConfigurerAdapter {
        private final ApplicationContext context;
        private static final Log logger = LogFactory.getLog(EnableGlobalAuthenticationAutowiredConfigurer.class);

        EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
            this.context = context;
        }

        @Override
        public void init(AuthenticationManagerBuilder auth) {
            // 获取所有带有@EnableGlobalAuthentication注解的全局配置
            Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(EnableGlobalAuthentication.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly initializing " + beansWithAnnotation);
            }
        }
    }

    /**
     * Prevents infinite recursion in the event that initializing the
     * AuthenticationManager.
     *
     * @author Rob Winch
     * @since 4.1.1
     */
    static final class AuthenticationManagerDelegator implements AuthenticationManager {
        private AuthenticationManagerBuilder delegateBuilder;
        private AuthenticationManager delegate;
        private final Object delegateMonitor = new Object();

        AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {
            Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
            this.delegateBuilder = delegateBuilder;
        }

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 如果已经包含创建成功的AuthenticationManager，直接调用AuthenticationManager.authenticate()方法返回一个Authentication
            if (this.delegate != null) {
                return this.delegate.authenticate(authentication);
            }

            synchronized (this.delegateMonitor) {
                if (this.delegate == null) {
                    this.delegate = this.delegateBuilder.getObject();
                    this.delegateBuilder = null;
                }
            }
            // 调用AuthenticationManager.authenticate()方法返回一个Authentication
            return this.delegate.authenticate(authentication);
        }

        @Override
        public String toString() {
            return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]";
        }
    }

    static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder {
        private PasswordEncoder defaultPasswordEncoder;

        /**
         * Creates a new instance
         *
         * @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
         */
        DefaultPasswordEncoderAuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) {
            super(objectPostProcessor);
            this.defaultPasswordEncoder = defaultPasswordEncoder;
        }

        @Override
        public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception {
            return super.inMemoryAuthentication().passwordEncoder(this.defaultPasswordEncoder);
        }

        @Override
        public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
            return super.jdbcAuthentication().passwordEncoder(this.defaultPasswordEncoder);
        }

        @Override
        public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception {
            return super.userDetailsService(userDetailsService).passwordEncoder(this.defaultPasswordEncoder);
        }
    }

    static class LazyPasswordEncoder implements PasswordEncoder {
        private ApplicationContext applicationContext;
        private PasswordEncoder passwordEncoder;

        LazyPasswordEncoder(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public String encode(CharSequence rawPassword) {
            return getPasswordEncoder().encode(rawPassword);
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return getPasswordEncoder().matches(rawPassword, encodedPassword);
        }

        @Override
        public boolean upgradeEncoding(String encodedPassword) {
            return getPasswordEncoder().upgradeEncoding(encodedPassword);
        }

        private PasswordEncoder getPasswordEncoder() {
            if (this.passwordEncoder != null) {
                return this.passwordEncoder;
            }
            PasswordEncoder passwordEncoder = getBeanOrNull(this.applicationContext, PasswordEncoder.class);
            if (passwordEncoder == null) {
                passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
            }
            this.passwordEncoder = passwordEncoder;
            return passwordEncoder;
        }

        @Override
        public String toString() {
            return getPasswordEncoder().toString();
        }
    }
}